import re
from enum import Enum
from kimidev.harness.constants import TestStatus


def parse_log_pytest(log: str) -> dict[str, str]:
    """
    Parser for test logs generated with PyTest framework

    Args:
        log (str): log content
    Returns:
        dict: test case to test status mapping
    """
    test_status_map = {}
    for line in log.split('\n'):
        if any([line.startswith(x.value) for x in TestStatus]):
            # Additional parsing for FAILED status
            if line.startswith(TestStatus.FAILED.value):
                line = line.replace(' - ', ' ')
            test_case = line.split()
            if len(test_case) <= 1:
                continue
            test_status_map[test_case[1]] = test_case[0]
    return test_status_map


def parse_log_pytest_options(log: str) -> dict[str, str]:
    """
    Parser for test logs generated with PyTest framework with options

    Args:
        log (str): log content
    Returns:
        dict: test case to test status mapping
    """
    option_pattern = re.compile(r'(.*?)\[(.*)\]')
    test_status_map = {}
    for line in log.split('\n'):
        if any([line.startswith(x.value) for x in TestStatus]):
            # Additional parsing for FAILED status
            if line.startswith(TestStatus.FAILED.value):
                line = line.replace(' - ', ' ')
            test_case = line.split()
            if len(test_case) <= 1:
                continue
            has_option = option_pattern.search(test_case[1])
            if has_option:
                main, option = has_option.groups()
                if option.startswith('/') and not option.startswith('//') and '*' not in option:
                    option = '/' + option.split('/')[-1]
                test_name = f'{main}[{option}]'
            else:
                test_name = test_case[1]
            test_status_map[test_name] = test_case[0]
    return test_status_map


def parse_log_django(log: str) -> dict[str, str]:
    """
    Parser for test logs generated with Django tester framework

    Args:
        log (str): log content
    Returns:
        dict: test case to test status mapping
    """
    test_status_map = {}
    lines = log.split('\n')

    prev_test = None
    for line in lines:
        line = line.strip()

        # This isn't ideal but the test output spans multiple lines
        if '--version is equivalent to version' in line:
            test_status_map['--version is equivalent to version'] = TestStatus.PASSED.value

        # Log it in case of error
        if ' ... ' in line:
            prev_test = line.split(' ... ')[0]

        pass_suffixes = (' ... ok', ' ... OK', ' ...  OK')
        for suffix in pass_suffixes:
            if line.endswith(suffix):
                # TODO: Temporary, exclusive fix for django__django-7188
                # The proper fix should involve somehow getting the test results to
                # print on a separate line, rather than the same line
                # if line.strip().startswith("Applying sites.0002_alter_domain_unique...test_no_migrations"):
                # line = line.split("...", 1)[-1].strip()

                # Migrating here
                if line.count('...') > 1:
                    line = line.split('...', 1)[-1].strip()

                test = line.rsplit(suffix, 1)[0]
                test_status_map[test] = TestStatus.PASSED.value
                break
        if ' ... skipped' in line:
            test = line.split(' ... skipped')[0]
            test_status_map[test] = TestStatus.SKIPPED.value
        if line.endswith(' ... FAIL'):
            test = line.split(' ... FAIL')[0]
            test_status_map[test] = TestStatus.FAILED.value
        if line.startswith('FAIL:'):
            test = line.strip()[len('FAIL: ') :]
            # test = line.split()[1].strip()
            test_status_map[test] = TestStatus.FAILED.value
        if line.endswith(' ... ERROR'):
            test = line.split(' ... ERROR')[0]
            test_status_map[test] = TestStatus.ERROR.value
        if line.startswith('ERROR:'):
            test = line.strip()[len('ERROR: ') :]
            # test = line.split()[1].strip()
            test_status_map[test] = TestStatus.ERROR.value

        if line.lstrip().startswith('ok') and prev_test is not None:
            # It means the test passed, but there's some additional output (including new lines)
            # between "..." and "ok" message
            test = prev_test
            test_status_map[test] = TestStatus.PASSED.value

    # TODO: This is very brittle, we should do better
    # There's a bug in the django logger, such that sometimes a test output near the end gets
    # interrupted by a particular long multiline print statement.
    # We have observed this in one of 3 forms:
    # - "{test_name} ... Testing against Django installed in {*} silenced.\nok"
    # - "{test_name} ... Internal Server Error: \/(.*)\/\nok"
    # - "{test_name} ... System check identified no issues (0 silenced).\nok"
    patterns = [
        r'^(.*?)\s\.\.\.\sTesting\ against\ Django\ installed\ in\ ((?s:.*?))\ silenced\)\.\nok$',
        r'^(.*?)\s\.\.\.\sInternal\ Server\ Error:\ \/(.*)\/\nok$',
        r'^(.*?)\s\.\.\.\sSystem check identified no issues \(0 silenced\)\nok$',
    ]
    for pattern in patterns:
        for match in re.finditer(pattern, log, re.MULTILINE):
            test_name = match.group(1)
            test_status_map[test_name] = TestStatus.PASSED.value
    return test_status_map


def parse_log_pytest_v2(log: str) -> dict[str, str]:
    """
    Parser for test logs generated with PyTest framework (Later Version)

    Args:
        log (str): log content
    Returns:
        dict: test case to test status mapping
    """
    test_status_map = {}
    escapes = ''.join([chr(char) for char in range(1, 32)])
    for line in log.split('\n'):
        line = re.sub(r'\[(\d+)m', '', line)
        translator = str.maketrans('', '', escapes)
        line = line.translate(translator)
        if any([line.startswith(x.value) for x in TestStatus]):
            if line.startswith(TestStatus.FAILED.value):
                line = line.replace(' - ', ' ')
            test_case = line.split()
            if len(test_case) >= 2:
                test_status_map[test_case[1]] = test_case[0]
        # Support older pytest versions by checking if the line ends with the test status
        elif any([line.endswith(x.value) for x in TestStatus]):
            test_case = line.split()
            if len(test_case) >= 2:
                test_status_map[test_case[0]] = test_case[1]
    return test_status_map


def parse_log_seaborn(log: str) -> dict[str, str]:
    """
    Parser for test logs generated with seaborn testing framework

    Args:
        log (str): log content
    Returns:
        dict: test case to test status mapping
    """
    test_status_map = {}
    for line in log.split('\n'):
        if line.startswith(TestStatus.FAILED.value):
            test_case = line.split()[1]
            test_status_map[test_case] = TestStatus.FAILED.value
        elif f' {TestStatus.PASSED.value} ' in line:
            parts = line.split()
            if parts[1] == TestStatus.PASSED.value:
                test_case = parts[0]
                test_status_map[test_case] = TestStatus.PASSED.value
        elif line.startswith(TestStatus.PASSED.value):
            parts = line.split()
            test_case = parts[1]
            test_status_map[test_case] = TestStatus.PASSED.value
    return test_status_map


def parse_log_sympy(log: str) -> dict[str, str]:
    """
    Parser for test logs generated with Sympy framework

    Args:
        log (str): log content
    Returns:
        dict: test case to test status mapping
    """
    test_status_map = {}
    for line in log.split('\n'):
        line = line.strip()
        if line.startswith('test_'):
            if line.endswith('[FAIL]') or line.endswith('[OK]'):
                line = line[: line.rfind('[')]
                line = line.strip()
            if line.endswith(' E'):
                test = line.split()[0]
                test_status_map[test] = TestStatus.ERROR.value
            if line.endswith(' F'):
                test = line.split()[0]
                test_status_map[test] = TestStatus.FAILED.value
            if line.endswith(' ok'):
                test = line.split()[0]
                test_status_map[test] = TestStatus.PASSED.value

    pattern = r'(_*) (.*)\.py:(.*) (_*)'
    matches = re.findall(pattern, log)
    for match in matches:
        if match[2] not in test_status_map:
            test_case = f'{match[1]}.py:{match[2]}'
            test_status_map[test_case] = TestStatus.FAILED.value

    return test_status_map


def parse_log_matplotlib(log: str) -> dict[str, str]:
    """
    Parser for test logs generated with PyTest framework

    Args:
        log (str): log content
    Returns:
        dict: test case to test status mapping
    """
    test_status_map = {}
    for line in log.split('\n'):
        line = line.replace('MouseButton.LEFT', '1')
        line = line.replace('MouseButton.RIGHT', '3')
        if any([line.startswith(x.value) for x in TestStatus]):
            # Additional parsing for FAILED status
            if line.startswith(TestStatus.FAILED.value):
                line = line.replace(' - ', ' ')
            test_case = line.split()
            if len(test_case) <= 1:
                continue
            test_status_map[test_case[1]] = test_case[0]
    return test_status_map

def parse_log_mypy(log: str) -> dict[str, str]:
    """Parser for test logs generated by mypy"""
    test_status_map = {}
    for line in log.split("\n"):
        for status in [
            TestStatus.PASSED.value,
            TestStatus.FAILED.value,
        ]:
            if status in line:
                test_case = line.split()[-1]
                test_status_map[test_case] = status
                break
    return test_status_map


def parse_log_python_slugify(log: str) -> dict[str, str]:
    """Parser for test logs generated by un33k/python-slugify"""
    test_status_map = {}
    pattern = r"^([a-zA-Z0-9_\-,\.\s\(\)']+)\s\.{3}\s"
    for line in log.split("\n"):
        is_match = re.match(f"{pattern}ok$", line)
        if is_match:
            test_status_map[is_match.group(1)] = TestStatus.PASSED.value
            continue
        for keyword, status in {
            "FAIL": TestStatus.FAILED,
            "ERROR": TestStatus.ERROR,
        }.items():
            is_match = re.match(f"{pattern}{keyword}$", line)
            if is_match:
                test_status_map[is_match.group(1)] = status.value
                continue
    return test_status_map


def parse_log_tornado(log: str) -> dict[str, str]:
    """Parser for test logs generated by tornadoweb/tornado"""
    test_status_map = {}
    for line in log.split("\n"):
        if line.endswith("... ok"):
            test_case = line.split(" ... ")[0]
            test_status_map[test_case] = TestStatus.PASSED.value
        elif " ... skipped " in line:
            test_case = line.split(" ... ")[0]
            test_status_map[test_case] = TestStatus.SKIPPED.value
        elif any([line.startswith(x) for x in ["ERROR:", "FAIL:"]]):
            test_case = " ".join(line.split()[1:3])
            test_status_map[test_case] = TestStatus.FAILED.value
    return test_status_map


def parse_log_paramiko(log: str) -> dict[str, str]:
    """Parser for test logs generated by paramiko/paramiko"""
    test_status_map = {}
    for line in log.split("\n"):
        for status in TestStatus:
            is_match = re.match(rf"^{status.value}\s(\S+)", line)
            if is_match:
                test_status_map[is_match.group(1)] = status.value
                continue
    return test_status_map


def parse_log_autograd(log: str) -> dict[str, str]:
    """Parser for test logs generated by pytorch/pytorch"""
    test_status_map = {}
    for line in log.split("\n"):
        for status in TestStatus:
            is_match = re.match(rf"^\[gw\d\]\s{status.value}\s(\S+)", line)
            if is_match:
                test_status_map[is_match.group(1)] = status.value
                continue
    return test_status_map


parse_log_astroid = parse_log_pytest
parse_log_flask = parse_log_pytest
parse_log_marshmallow = parse_log_pytest
parse_log_pvlib = parse_log_pytest
parse_log_pyvista = parse_log_pytest
parse_log_sqlfluff = parse_log_pytest
parse_log_xarray = parse_log_pytest

parse_log_pydicom = parse_log_pytest_options
parse_log_requests = parse_log_pytest_options
parse_log_pylint = parse_log_pytest_options

parse_log_astropy = parse_log_pytest_v2
parse_log_scikit = parse_log_pytest_v2
parse_log_sphinx = parse_log_pytest_v2

parse_log_moto     = parse_log_pytest
parse_log_mypy     = parse_log_pytest
parse_log_dvc      = parse_log_pytest
parse_log_pandas   = parse_log_pytest
parse_log_monai    = parse_log_pytest
parse_log_bokeh    = parse_log_pytest
parse_log_hydra    = parse_log_pytest
parse_log_conan    = parse_log_pytest
# parse_log_pydantic = parse_log_pytest
parse_log_pydantic = parse_log_pytest_v2
parse_log_modin    = parse_log_pytest
parse_log_dask     = parse_log_pytest

def parse_log_pytest_swesmith(log: str) -> dict[str, str]:
    """
    Parser for test logs generated with PyTest framework

    Args:
        log (str): log content
    Returns:
        dict: test case to test status mapping
    """
    test_status_map = {}
    for line in log.split("\n"):
        for status in TestStatus:
            is_match = re.match(rf"^(\S+)(\s+){status.value}", line)
            if is_match:
                test_status_map[is_match.group(1)] = status.value
                continue
    return test_status_map


MAP_REPO_TO_PARSER = {
    "astropy/astropy": parse_log_astropy,
    "django/django": parse_log_django,
    "marshmallow-code/marshmallow": parse_log_marshmallow,
    "matplotlib/matplotlib": parse_log_matplotlib,
    "mwaskom/seaborn": parse_log_seaborn,
    "pallets/flask": parse_log_flask,
    "psf/requests": parse_log_requests,
    "pvlib/pvlib-python": parse_log_pvlib,
    "pydata/xarray": parse_log_xarray,
    "pydicom/pydicom": parse_log_pydicom,
    "pylint-dev/astroid": parse_log_astroid,
    "pylint-dev/pylint": parse_log_pylint,
    "pytest-dev/pytest": parse_log_pytest,
    "pyvista/pyvista": parse_log_pyvista,
    "scikit-learn/scikit-learn": parse_log_scikit,
    "sqlfluff/sqlfluff": parse_log_sqlfluff,
    "sphinx-doc/sphinx": parse_log_sphinx,
    "sympy/sympy": parse_log_sympy,
    "getmoto/moto"          : parse_log_moto,
    "python/mypy"           : parse_log_mypy,
    "iterative/dvc"         : parse_log_dvc,
    "pandas-dev/pandas"     : parse_log_pandas,
    "Project-MONAI/MONAI"   : parse_log_monai,
    "bokeh/bokeh"           : parse_log_bokeh,
    "facebookresearch/hydra": parse_log_hydra,
    "conan-io/conan"        : parse_log_conan,
    "pydantic/pydantic"     : parse_log_pydantic,
    "modin-project/modin"   : parse_log_modin,
    "dask/dask"             : parse_log_dask,    
    "HIPS/autograd": parse_log_autograd,
    "paramiko/paramiko": parse_log_paramiko,
    "python/mypy": parse_log_mypy,
    "tornadoweb/tornado": parse_log_tornado,
    "un33k/python-slugify": parse_log_python_slugify,    
}
